#include "sio.h"


#include <assert.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


SIO *sio_init(SIO *io) {
    io->deletor = NULL;
    io->reader = NULL;
    io->writer = NULL;
    io->p = io->buff = io->buffend = NULL;
    sio_clearerror(io);
    return io;
}

void sio_cleanup(SIO *io) {
    if (io->deletor)
        io->deletor(io);
}


/* read operations */

static const char *mask = 
    "                                "
    "                0123456789      "
    " :;<=>?@ABCDEFGHIJKLMNOPQRS     "
    " :;<=>?@ABCDEFGHIJKLMNOPQRS     ";

int sio_fill(SIO *io) {
    size_t size;
    if (io->reader == NULL) {
        io->p = io->buff = io->buffend = NULL;
        return 0;
    }
    sio_clearerror(io);
    io->p = io->buff = io->reader(io, &size);
    io->buffend = io->buff == NULL ? NULL : io->buff+size;
    return io->errnum == 0;
}

void sio_setreader(SIO *io, sio_Reader *reader) {
    io->reader = reader;
    sio_fill(io);
}

static int parse_sig(SIO *io, size_t *pcount) {
    int ch = sio_peek(io);
    int sig = 1;
    while (ch == '-' || ch == '+') {
        if (ch == '-') sig = -sig;
        ++*pcount;
        sio_next(io);
        ch = sio_peek(io);
    }
    return sig;
}

static int parse_base(SIO *io, size_t *pcount) {
    int base = 10;
    int ch = sio_peek(io);
    if (ch == '0') {
        ++*pcount;
        sio_next(io);
        switch (ch = sio_peek(io)) {
        default: return 10;
        case 'd': case 'D': base = 10; break;
        case 'b': case 'B': base = 2; break;
        case 'o': case 'O': base = 8; break;
        case 'x': case 'X': base = 16; break;
        }
        ++*pcount;
        sio_next(io);
    }
    return base;
}

static int parse_int(SIO *io, int base, size_t *pcount) {
    int num = 0;
    int ch = sio_peek(io);
    while (ch < 0x7F && (ch = mask[ch]) != ' ') {
        ch -= '0';
        if (ch > base) break;
        num *= base;
        ++*pcount;
        sio_next(io);
        ch = sio_peek(io);
    }
    return num;
}

static int parse_exp(SIO *io, int base, size_t *pcount) {
    int ch = sio_peek(io);
    if ((base == 10 && (ch == 'e' || ch == 'E')) ||
            (base == 16 && (ch == 'p' || ch == 'P'))) {
        int sig, exp;
        ++*pcount;
        sio_next(io);
        sig = parse_sig(io, pcount);
        exp = parse_int(io, 10, pcount);
        return sig * exp;
    }
    return 0;
}

int sio_readint(SIO *io, int base, size_t *pcount) {
    int num = 0;
    size_t count = 0;
    int sig = parse_sig(io, &count);
    if (base == 0)
        base = parse_base(io, &count);
    num = parse_int(io, base, &count);
    if (pcount) *pcount = count;
    return num*sig;
}

double sio_readfloat(SIO *io, size_t *pcount) {
    size_t count = 0;
    double sig = (double)parse_sig(io, &count);
    double num = 0.0;
    int base = parse_base(io, &count);

    if (base == 10 || base == 16) {
        int en = parse_exp(io, base, &count);
        num = (double)parse_int(io, base, &count);

        if (en != 0) {
            if (base == 16)
                num = ldexp(num, en);
            else
                num *= pow(10, en);
        }
    }

    if (pcount) *pcount = count;
    return num*sig;
}

int sio_readint_binary(SIO *io, int bigendian, size_t count) {
    size_t i;
    int num = 0;
    for (i = 0; i < count; ++i) {
        int ch = sio_peek(io);
        if (!sio_next(io)) break;
        if (bigendian) {
            num |= ch & UCHAR_MAX;
            num <<= CHAR_BIT;
        }
        else if (i < sizeof(int))
            num |= (ch & UCHAR_MAX) << (i*CHAR_BIT);
    }
    return num;
}

double sio_readfloat_binary(SIO *io, int bigendian, size_t count) {
    return 0;
}

size_t sio_read(SIO *io, char *buff, size_t sz) {
    size_t count = 0;
    size_t bufflen = io->buffend - io->p;
    if (bufflen == 0) return 0;
    while (bufflen < sz) {
        memcpy(buff, io->p, bufflen);
        count += bufflen;
        buff += bufflen;
        sz -= bufflen;
        if (!sio_fill(io))
            return count;
        bufflen = io->buffend - io->p;
    }
    memcpy(buff, io->p, sz);
    io->p += sz;
    return count + sz;
}


/* write operations */

static const char *numtab = 
    "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

#define NUMBUFF_MAX 64
#define FMTITEM_MAX 512

void sio_setwriter(SIO *io, sio_Writer *writer) {
    io->writer = writer;
    io->p = io->buff = io->rwbuff;
    io->buffend = io->buff + sizeof(io->rwbuff);
}

size_t sio_flush(SIO *io) {
    size_t bytes = 0;
    sio_clearerror(io);
    if (io->writer && io->p != io->buff)
        bytes = io->writer(io, io->buff, io->p - io->buff);
    io->p = io->buff = io->rwbuff;
    io->buffend = io->buff + sizeof(io->rwbuff);
    return bytes;
}

static size_t write_sig(SIO *io, int *pnum) {
    if (*pnum < 0) {
        sio_writechar(io, '-');
        *pnum = -*pnum;
        return 1;
    }
    return 0;
}

static size_t write_prefix(SIO *io, int base) {
    char buff[2] = "00";
    switch (base) {
    case 2: buff[1] = 'b'; break;
    case 8: buff[1] = 'o'; break;
    case 16: buff[1] = 'x'; break;
    default: return 0;
    }
    sio_writechar(io, buff[0]);
    sio_writechar(io, buff[1]);
    return 2;
}

static size_t write_int(SIO *io, int num, int base) {
    char buff[NUMBUFF_MAX], *p = buff + sizeof(buff);
    while (num != 0) {
        int n = num % base;
        assert(p > buff);
        assert(n < sizeof(numtab)-1);
        *--p = numtab[n];
        num /= base;
    }
    return sio_write(io, p, buff+sizeof(buff)-p);
}

size_t sio_writeint(SIO *io, int num, int base) {
    size_t written = write_sig(io, &num);
    if (base < 0) {
        base = -base;
        written += write_prefix(io, base);
    }
    if (base > 36) base = 36;
    written += write_int(io, num, base);
    return written;
}

size_t sio_writefloat(SIO *io, double num, int base) {
    char buff[FMTITEM_MAX];
    if (base == 16) {
        const char *fmt = "%a";
        sprintf(buff, fmt, num);
    }
    else
        sprintf(buff, "%.14g", num);
    return sio_write(io, buff, strlen(buff));
}

size_t sio_writeint_binary(SIO *io, int num, int bigendian, size_t count) {
    size_t i;
    /* TODO */
    unsigned mask = 0xFF << (CHAR_BIT*(sizeof(num)-1));
    if (!bigendian) {
        for (i = 0; i < count; ++i) {
            sio_writechar(io, num & UCHAR_MAX);
            num >>= CHAR_BIT;
        }
    }
    else {
        for (i = 0; i < count; ++i) {
            sio_writechar(io, (num & mask) >> (CHAR_BIT*(sizeof(num)-i-1)));
            mask >>= CHAR_BIT;
        }
    }
    return count;
}

size_t sio_writefloat_binary(SIO *io, double num, int endian, size_t count) {
    /* XXX TODO */
    return 0;
}

size_t sio_write(SIO *io, const char *buff, size_t sz) {
    size_t bufflen = io->p - io->buff;
    size_t inbuff = sz + bufflen < SIO_BUFSIZ ? sz : SIO_BUFSIZ-bufflen-sz;
    memcpy((char*)io->p, buff, inbuff);
    io->p += inbuff;
    buff += inbuff;
    sz -= inbuff;
    if (sz == 0) return inbuff;
    inbuff = sio_flush(io);
    if (io->errnum != 0)
        return inbuff < bufflen ? 0 : inbuff - bufflen;
    if (sz > SIO_BUFSIZ) {
        io->buff = buff;
        io->p = buff+sz;
        return sio_flush(io);
    }
    memcpy((char*)io->p, buff, sz);
    io->p += sz;
    return sz;
}


/* ordinary creators */

typedef struct SIO_fp {
    SIO io;
    FILE *fp;
} SIO_fp;

typedef struct SIO_buffer {
    SIO io;
    char *head, *buff;
    size_t len;
} SIO_buffer;

static void file_deletor(SIO *io) {
    fclose(((SIO_fp*)io)->fp);
    free(io);
}

static const char *file_reader(SIO *io, size_t *psz) {
    SIO_fp *lf = (SIO_fp*)io;
    if (feof(lf->fp)) return NULL;
    *psz = fread(io->rwbuff, 1, sizeof(io->rwbuff), lf->fp);
    return io->rwbuff;
}

static size_t file_writer(SIO *io, const char *buff, size_t sz) {
    SIO_fp *lf = (SIO_fp*)io;
    if (ferror(lf->fp)) {
        sio_seterror(io, errno, strerror(errno));
        return 0;
    }
    return fwrite(buff, 1, sz, lf->fp);
}

static void buff_deletor(SIO *io) {
    free(io);
}

static const char *buff_reader(SIO *io, size_t *psz) {
    SIO_buffer *lb = (SIO_buffer*)io;
    if (lb->len != 0) {
        *psz = lb->len;
        lb->len = 0;
        return lb->buff;
    }
    return NULL;
}

static size_t buff_writer(SIO *io, const char *buff, size_t sz) {
    size_t written;
    SIO_buffer *lb = (SIO_buffer*)io;
    if (lb->len == 0)
        return 0;
    written = lb->len >= sz ? sz : sz - lb->len;
    memcpy(lb->buff, buff, written);
    lb->buff += written;
    lb->len -= written;
    return written;
}

SIO *sio_from_filename(const char *fn, int binary) {
    FILE *fp;
    SIO_fp *lf = (SIO_fp*)malloc(sizeof(SIO_fp));
    if (lf == NULL) return NULL;
    fp = fopen(fn, binary ? "rb" : "r");
    if (fp == NULL) {
        free(lf);
        return NULL;
    }
    lf->fp = fp;
    sio_setdeletor(&lf->io, file_deletor);
    sio_setreader(&lf->io, file_reader);
    return &lf->io;
}

SIO *sio_from_buffer(const char *buff, size_t len) {
    SIO_buffer *lb = (SIO_buffer*)malloc(sizeof(SIO_buffer));
    if (lb == NULL) return NULL;
    lb->head = lb->buff = (char*)buff;
    lb->len = len;
    sio_setdeletor(&lb->io, buff_deletor);
    sio_setreader(&lb->io, buff_reader);
    return &lb->io;
}

SIO *sio_from_FILE(void *fp) {
    SIO_fp *lf = (SIO_fp*)malloc(sizeof(SIO_fp));
    if (lf == NULL) return NULL;
    lf->fp = (FILE*)fp;
    sio_setdeletor(&lf->io, buff_deletor);
    sio_setreader(&lf->io, file_reader);
    return &lf->io;
}

SIO *sio_to_filename(const char *fn, int binary) {
    FILE *fp;
    SIO_fp *lf = (SIO_fp*)malloc(sizeof(SIO_fp));
    if (lf == NULL) return NULL;
    fp = fopen(fn, binary ? "wb" : "w");
    if (fp == NULL) {
        free(lf);
        return NULL;
    }
    lf->fp = fp;
    sio_setdeletor(&lf->io, file_deletor);
    sio_setwriter(&lf->io, file_writer);
    return &lf->io;
}

SIO *sio_to_buffer(char *buff, size_t len) {
    SIO_buffer *lb = (SIO_buffer*)malloc(sizeof(SIO_buffer));
    if (lb == NULL) return NULL;
    lb->head = lb->buff = (char*)buff;
    lb->len = len;
    sio_setdeletor(&lb->io, buff_deletor);
    sio_setwriter(&lb->io, buff_writer);
    return &lb->io;
}

SIO *sio_to_FILE(void *fp) {
    SIO_fp *lf = (SIO_fp*)malloc(sizeof(SIO_fp));
    if (lf == NULL) return NULL;
    lf->fp = (FILE*)fp;
    sio_setdeletor(&lf->io, buff_deletor);
    sio_setwriter(&lf->io, file_writer);
    return &lf->io;
}

